#include "ota.h"

#define TAG "ota"
#define OTA_URL "https://api.tenclass.net/xiaozhi/ota/"

#define OTA_ACTIVATION_BIT ((EventBits_t)0x01)

EventGroupHandle_t eg;
my_ota_t           my_ota;

void ota_parse_response(void);
void ota_task(void *);
void ota_check_version(void)
{
    eg = xEventGroupCreate();
    // 创建一个单独的任务区做ota
    xTaskCreate(ota_task, "ota_task", 6 * 1024, NULL, 5, NULL);
    // 一行
    // 函数返回的时机: 设备激活成功
    xEventGroupWaitBits(eg, OTA_ACTIVATION_BIT, pdTRUE, pdTRUE, portMAX_DELAY);
    MY_LOGI("激活完成, 继续执行");
}

static char *output_buffer;   // Buffer to store response of http request from event handler
static int   output_len;      // Stores number of bytes read
esp_err_t    _http_event_handler(esp_http_client_event_t *evt)
{

    switch(evt->event_id)
    {
        case HTTP_EVENT_ERROR:
            ESP_LOGI(TAG, "HTTP_EVENT_ERROR");
            break;
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED");
            break;
        case HTTP_EVENT_HEADER_SENT:
            ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT");
            break;
        case HTTP_EVENT_ON_HEADER:   // 收到响应头
            ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
            // 给最终存储数据的缓冲区分配内存
            if(strcmp(evt->header_key, "Content-Length") == 0)
            {
                if(output_buffer) free(output_buffer);                 // 如果已经分配过空间, 则先释放
                output_buffer = malloc(atoi(evt->header_value) + 1);   // "577"=>577
                output_len    = 0;
            }
            break;
        case HTTP_EVENT_ON_DATA:   // 收到响应数据
            ESP_LOGI(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
            // MY_LOGI("%.*s", evt->data_len, (char *)evt->data);
            //  把每次收到的数据copy到最终的缓冲区
            memcpy(output_buffer + output_len, evt->data, evt->data_len);
            output_len += evt->data_len;   // 累计已收到的数据长度
            break;
        case HTTP_EVENT_ON_FINISH:
            ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH");

            break;
        case HTTP_EVENT_DISCONNECTED:
            ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");

            break;
        case HTTP_EVENT_REDIRECT:   // 重定向
            ESP_LOGI(TAG, "HTTP_EVENT_REDIRECT");
            break;
    }
    return ESP_OK;
}

static char *get_device_id(void)
{
    uint8_t mac[6];
    esp_read_mac(mac, ESP_MAC_WIFI_STA);

    // 动态内存分配(注意用完要释放)
    char *mac_str = (char *)malloc(18);
    sprintf(mac_str,
            "%02x:%02x:%02x:%02x:%02x:%02x",
            mac[0],
            mac[1],
            mac[2],
            mac[3],
            mac[4],
            mac[5]);
    return mac_str;
}

static char *get_client_id(void)
{

    // 打开nvs
    nvs_handle_t handle;
    nvs_open("ota", NVS_READWRITE, &handle);

    size_t uuid_len = 0;
    nvs_get_str(handle, "uuid", NULL, &uuid_len);   // 获取 uuid 长度
    if(uuid_len > 0)                                // uuid在flash中是存在的
    {
        char *uuid = (char *)malloc(uuid_len + 1);
        nvs_get_str(handle, "uuid", uuid, &uuid_len);   // 这次是真正的读出uuid的值
        nvs_close(handle);
        return uuid;
    }

    unsigned char bytes[16];
    for(int i = 0; i < 16; i++)
    {
        bytes[i] = rand() % 256;
    }

    // 设置 version (第 7 个字节高 4 位为 0100)
    bytes[6] = (bytes[6] & 0x0F) | 0x40;
    // 设置 variant (第 9 个字节高 2 位为 10)
    bytes[8] = (bytes[8] & 0x3F) | 0x80;

    char *uuid = (char *)malloc(37);
    sprintf(uuid,
            "%02x%02x%02x%02x-"
            "%02x%02x-"
            "%02x%02x-"
            "%02x%02x-"
            "%02x%02x%02x%02x%02x%02x",
            bytes[0],
            bytes[1],
            bytes[2],
            bytes[3],
            bytes[4],
            bytes[5],
            bytes[6],
            bytes[7],
            bytes[8],
            bytes[9],
            bytes[10],
            bytes[11],
            bytes[12],
            bytes[13],
            bytes[14],
            bytes[15]);
    // 存储到nvs中
    nvs_set_str(handle, "uuid", uuid);
    nvs_commit(handle);
    nvs_close(handle);
    return uuid;
}

void ota_set_request_header(esp_http_client_handle_t client)
{
    esp_http_client_set_header(client, "Content-Type", "application/json");
    esp_http_client_set_header(client, "User-Agent", "bread-compact-wifi-128x64/1.0.1");

    char *device_id = get_device_id();
    esp_http_client_set_header(client, "Device-id", device_id);

    char *client_id = get_client_id();
    esp_http_client_set_header(client, "Client-id", client_id);

    MY_LOGI("device_id: %s", device_id);
    MY_LOGI("client_id: %s", client_id);

    my_ota.client_id = strdup(client_id);   // 深度copy
    my_ota.device_id = strdup(device_id);

    free(device_id);
    free(client_id);
}

void ota_set_request_body(esp_http_client_handle_t client)
{

    cJSON *root = cJSON_CreateObject();

    cJSON *app = cJSON_CreateObject();

    cJSON_AddStringToObject(app, "version", "1.0.1");
    cJSON_AddStringToObject(app, "elf_sha256", esp_app_get_elf_sha256_str());
    cJSON_AddItemToObject(root, "application", app);

    cJSON *board = cJSON_CreateObject();
    cJSON_AddStringToObject(board, "type", "bread-compact-wifi");
    cJSON_AddStringToObject(board, "name", "bread-compact-wifi-128X64");
    cJSON_AddStringToObject(board, "ssid", "lzcxxx");
    cJSON_AddNumberToObject(board, "rssi", -55);
    cJSON_AddNumberToObject(board, "channel", 1);
    cJSON_AddStringToObject(board, "ip", "192.168.1.1");
    cJSON_AddStringToObject(board, "mac", my_ota.device_id);
    cJSON_AddItemToObject(root, "board", board);

    char *body = cJSON_PrintUnformatted(root);

    MY_LOGI("[ota] post body: %s", body);
    esp_http_client_set_post_field(client, body, strlen(body));

    cJSON_Delete(root);
}

void ota_task(void *args)
{
    esp_http_client_config_t config = {
        .url               = OTA_URL,
        .event_handler     = _http_event_handler,
        .method            = HTTP_METHOD_POST,       // post请求
        .crt_bundle_attach = esp_crt_bundle_attach   // 验证服务器证书
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    // 1. 设置请求头
    ota_set_request_header(client);

    // 2. 设置请求体
    ota_set_request_body(client);
    while(1)
    {
        // 3. 发送请求
        esp_err_t err = esp_http_client_perform(client);
        if(err == ESP_OK)
        {
            // 200:ok    404:not found 500:server error
            // MY_LOGI("请求成功, 响应码: %d, 响应长度: %" PRId64,
            //        esp_http_client_get_status_code(client),
            //       esp_http_client_get_content_length(client));
            // 输出最终响应结果
            MY_LOGI("响应结果: %.*s", output_len, output_buffer);
            // 解析响应结果
            ota_parse_response();

            // 给我打印输出 my_ota的内容
            // MY_LOGI("my_ota: %s %s %s", my_ota.activation_code, my_ota.token, my_ota.ws_url);
            if(my_ota.activation_code)
            {
                MY_LOGI("请迅速激活设备, 激活码: %s", my_ota.activation_code);
                my_ota.activation_code = NULL;
            }
            else
            {
                MY_LOGI("设备已激活");
                xEventGroupSetBits(eg, OTA_ACTIVATION_BIT);
                break;
            }
        }
        else
        {
            ESP_LOGE(TAG, "HTTP POST request failed: %s", esp_err_to_name(err));
        }

        vTaskDelay(10000);
    }

    free(output_buffer);
    vTaskDelete(NULL);
}

void ota_parse_response(void)
{
    cJSON *root = cJSON_ParseWithLength(output_buffer, output_len);

    if(root == NULL)
    {
        MY_LOGE("响应结果解析失败: %.*s", output_len, output_buffer);
        return;
    }

    cJSON *websocket = cJSON_GetObjectItem(root, "websocket");
    if(websocket != NULL)
    {
        cJSON *url = cJSON_GetObjectItem(websocket, "url");
        if(url != NULL)
        {
            my_ota.ws_url = strdup(url->valuestring);
        }

        cJSON *token = cJSON_GetObjectItem(websocket, "token");
        if(token != NULL)
        {
            my_ota.token = strdup(token->valuestring);
        }
    }

    cJSON *activation = cJSON_GetObjectItem(root, "activation");
    if(activation != NULL)
    {
        cJSON *code = cJSON_GetObjectItem(activation, "code");
        if(code != NULL)
        {
            my_ota.activation_code = strdup(code->valuestring);
        }
    }

    cJSON_Delete(root);
}
